package com.lp.article.service.impl;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.lp.article.mapper.ArticleMapper;
import com.lp.article.mapper.CollectsMapper;
import com.lp.article.service.IArticleService;
import com.lp.article.service.ICollectService;
import com.lp.model.config.AppResult;
import com.lp.model.config.CodeMsg;
import com.lp.model.constants.MQConstants;
import com.lp.model.constants.RedisConstants;
import com.lp.model.dto.article.CollectDto;
import com.lp.model.dto.article.PageDto;
import com.lp.model.entity.article.Article;
import com.lp.model.entity.article.redis.CollectsMsg;
import com.lp.model.entity.article.redis.vo.CollectsMsgVO;
import com.lp.model.entity.article.vo.ArticleESVO;
import com.lp.model.entity.article.vo.PageVO;
import com.lp.model.exception.ApplicationException;
import com.lp.model.utils.PageUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class CollectService implements ICollectService {

    @Autowired
    private CollectsMapper collectsMapper;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private ArticleMapper articleMapper;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private IArticleService articleService;

    @Override
    public void collect(Long userId, CollectDto collectDto) {
        //1.非空校验
        if(userId == null || collectDto == null || collectDto.getArticleId() == null) {
            log.info(CodeMsg.FAIL_NULL_COLLECT.toString());
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_NULL_COLLECT));
        }
        //2.通过 mq 实现异步收藏
        mqSendCollectMsg(userId, collectDto);
    }

    @Override
    public PageVO<Article> getCollectList(PageDto page) {
        //1.计算分页信息
        int limit = page.getPageSize();
        int offset = (page.getCurPage() - 1) * page.getPageSize();
        //2.获取收藏列表的文章信息
        //1) 获取收藏列表文章 id
        List<Long> articleIdList = collectsMapper.selectCollectListByUserId(page.getId(), limit, offset);
        //2) 获取文章信息
        List<Article> articles = getArticleListByIdList(articleIdList);
        //3) 获取该用户的收藏总数
        Long collectTotal = collectsMapper.selectCollectTotalByUserId(page.getId());
        //3.获取分页响应
        return PageUtils.getPageVO(articles, collectTotal);
    }

    @Override
    public PageVO<CollectsMsgVO> getMsgPage(PageDto pageDto) {
        int limit = pageDto.getPageSize();
        int offset = (pageDto.getCurPage() - 1) * pageDto.getPageSize();
        //1.从 redis 上获取收藏消息分页列表
        //1) 获取 json 列表
        List<String> collectMsgVOPageJson = redisTemplate.opsForList().range(RedisConstants.COLLECT_MSG_LIST + pageDto.getId(),
                offset, limit);
        if(collectMsgVOPageJson == null) {
            return PageUtils.getPageVO(new ArrayList<>(), 0L);
        }
        //2) 转化
        List<CollectsMsgVO> likeMsgVOPage = getCollectMsgVOPageByJson(collectMsgVOPageJson);
        //3) 获取总数
        Long total = redisTemplate.opsForList().size(RedisConstants.COLLECT_MSG_LIST + pageDto.getId());
        //2.更新用户消息状态(已读)
        redisTemplate.opsForSet().remove(RedisConstants.USER_COLLECT_MSG_STATE_SET,
                pageDto.getId().toString());
        //3.设置过期时间(一旦用户长时间不看，自动删除缓存)
        redisTemplate.expire(RedisConstants.COLLECT_MSG_LIST + pageDto.getId(),
                RedisConstants.UPDATE_MSG_EXPIRE, TimeUnit.DAYS);
        //4.整合 VO
        return PageUtils.getPageVO(likeMsgVOPage, total);
    }

    @SneakyThrows
    private List<CollectsMsgVO> getCollectMsgVOPageByJson(List<String> collectMsgVOPageJson) {
        List<CollectsMsgVO> result = new ArrayList<>();
        for(String collectMsgVO : collectMsgVOPageJson) {
            result.add(objectMapper.readValue(collectMsgVO, CollectsMsgVO.class));
        }
        return result;
    }

    @SneakyThrows
    @Override
    public void addMsg(CollectsMsg collectsMsg) {
        //1.在 redis 上增加点赞消息(约定左端消息最新)
        //1) 获取最终 VO
        CollectsMsgVO collectsMsgVO = getCollectMsgVO(collectsMsg);
        //2) 存储到 redis
        redisTemplate.opsForList().leftPush(RedisConstants.LIKE_MSG_LIST + collectsMsg.getUserId(),
                objectMapper.writeValueAsString(collectsMsgVO));
        //2.更新用户消息状态(存不存在都直接添加即可，因为 set 里没有重复元素)
        redisTemplate.opsForSet().add(RedisConstants.USER_LIKE_MSG_STATE_SET,
                collectsMsg.getUserId().toString());
        log.info("用户获得了新的点赞! {}", collectsMsg);
    }

    /**
     * 获取收藏消息的最终 VO(保存到 redis 上的数据)
     * @param collectsMsg 点赞消息 id 相关数据
     */
    private CollectsMsgVO getCollectMsgVO(CollectsMsg collectsMsg) {
        CollectsMsgVO result = new CollectsMsgVO();
        BeanUtils.copyProperties(collectsMsg, result);
        //1.获取 ESVO 数据(Article + User(nickname + photo))
        ArticleESVO articleESVO = articleService.getArticleESVO(collectsMsg.getPostId());
        if(articleESVO == null) {
            throw new ApplicationException(AppResult.fail(CodeMsg.FAIL_ERROR_ARTICLE_ID));
        }
        //2.整合 VO
        result.setNickName(articleESVO.getNickname());
        result.setPhoto(articleESVO.getPhotoUrl());
        result.setArticleTitle(articleESVO.getTitle());
        return result;
    }

    @Override
    public void delMsgAll(Long userId) {
        redisTemplate.delete(RedisConstants.COLLECT_MSG_LIST + userId);
    }

    @Override
    public boolean isRead(Long userId) {
        return !Boolean.TRUE.equals(redisTemplate.opsForSet().
                isMember(RedisConstants.USER_COLLECT_MSG_STATE_SET, userId.toString()));
    }

    /**
     * 根据文章 id 列表，获取文章列表
     * @param articleIdList
     * @return
     */
    private List<Article> getArticleListByIdList(List<Long> articleIdList) {
        List<Article> result = new ArrayList<>();
        for(Long articleId : articleIdList) {
            Article article = articleMapper.selectArticleById(articleId);
            if(article == null) { //文章被标记删除
                continue;
            }
            article.setContent("");//不需要正文
            result.add(article);
        }
        return result;
    }

    /**
     * 通过 mq 实现异步点赞
     * @param userId
     * @param collectDto
     */
    @SneakyThrows
    private void mqSendCollectMsg(Long userId, CollectDto collectDto) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("userId", userId);
        map.put("articleId", collectDto.getArticleId());
        String msg = objectMapper.writeValueAsString(map);
        rabbitTemplate.convertAndSend(MQConstants.ARTICLE_EXCHANGE_DIRECT,
                MQConstants.ARTICLE_COLLECT_QUEUE, msg);
    }

}
